#include <algorithm>

#include "gamestate.h"

#include "pathfinder.h"
#include "player.h"
#include "resourcemanager.h"

#include "entities/army.h"
#include "entities/building.h"
#include "entities/improvement.h"
#include "entities/city.h"
#include "entities/terraintype.h"
#include "entities/tile.h"
#include "entities/unit.h"

GameState* gamestate;

GameState::GameState(){
    mapWidth = 16;
    mapHeight = 16;
    turn = 0;
    active_player = 0;
    selected_entity = -1;
    active_entity = -1;

    for(unsigned int i=0; i<mapWidth*mapHeight; i++){
        AddTileToProvince(-1, i);
    }
}

void GameState::Initialise(){
}

void GameState::CreateMap(){
    for( int i=0; i<mapHeight; i++){
        for( int j=0; j<mapWidth; j++){
            std::unique_ptr<Tile> tile(new Tile(Index(j, i)));
            tile->SetTerrain("desert", "none");
            tiles.push_back(std::move(tile));
        }
    }
    AddPathToRiverNetwork(pathfinder.GetPath(50, 100));
    AddPathToRiverNetwork(pathfinder.GetPath(100, 110));
}

void GameState::CreatePlayer(bool ai){
    std::unique_ptr<Player> player(new Player());
    player->SetIsAI(ai);
    players.push_back(std::move(player));
}

// Entity Creation /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Improvement* GameState::CreateImprovement(){
	std::unique_ptr<Improvement> improvement(new Improvement());
	improvements.push_back(std::move(improvement));
    return improvements.back().get();
}

City* GameState::CreateCity(int index, int owner){
    cities.push_back(std::unique_ptr<City>(new City(index, owner)));
    City* city = cities.back().get();

    Tile* tile = GetTileByIndex(index);
    Building* building;

    std::cout<<"City ID: ";
    city->PrintID();

    building = CreateBuilding("citycenter");
    building->SetCityOwner(city->GetID());
    building->SetTurnsToComplete(0);
    city->AddBuilding(building->GetID());

    tile->SetImprovement(city->GetID());

    influenceChanged = true;

    return city;
}

Army* GameState::CreateArmy(){
    std::unique_ptr<Army> army(new Army());
    armies.push_back(std::move(army));
    return armies.back().get();
}

Unit* GameState::CreateUnit(std::string name){
    std::unique_ptr<Unit> unit(new Unit(*resourceManager->GetUnit(name)));
    units.push_back(std::move(unit));
    return units.back().get();
}

Building* GameState::CreateBuilding(std::string name){
    std::unique_ptr<Building> building;
    Building* source = resourceManager->GetBuilding(name);
    if(source == nullptr){
        std::cout<<"CreateBuilding Error: building '"<<name<<"' not found\n";
        return nullptr;
    }
    building = std::unique_ptr<Building>(new Building(*source));
    building->GenerateNewID();

    buildings.push_back(std::move(building));

    return buildings.back().get();
}

void GameState::AddPathToRoadNetwork(std::vector<int> path, int weight){
    for(unsigned int i=0; i<path.size()-1; i++){
        int a = path.at(i);
        int b = path.at(i+1);

        roads.Add(a, b, weight);
    }
}

void GameState::AddPathToRiverNetwork(std::vector<int> path){
    for(unsigned int i=0; i<path.size()-1; i++){
        int a = path.at(i);
        int b = path.at(i+1);

        rivers.Add(a, b, 0);
    }
}

// Entity Deletion /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void GameState::DeleteArmy(int id){
    for(unsigned int i=0;i<armies.size();i++){
        if(armies.at(i)->GetID()==id){
            armies.erase(armies.begin()+i);
            return;
        }
    }
}

void GameState::DeleteMarkedUnits(){
    for(auto unit:unitsToDelete){
        DeleteUnit(unit);
    }
}

bool GameState::DeleteUnit(int id){
    for(unsigned int i=0;i<units.size();i++){
        if(units.at(i)->GetID()==id){
            units.erase(units.begin()+i);
            return true;
        }
    }
    return false;
}

bool GameState::DeleteBuilding(int id){
    for(unsigned int i=0;i<buildings.size();i++){
        if(buildings.at(i)->GetID()==id){
            buildings.erase(buildings.begin()+i);
            return true;
        }
    }
    return false;
}

bool GameState::DeleteImprovement(int id){
    for(unsigned int i=0;i<improvements.size();i++){
        if(improvements.at(i)->GetID()==id){
            improvements.erase(improvements.begin()+i);
            return true;
        }
    }
    return false;
}

void GameState::DeleteMarkedEntities(){
    while(entitiesToDelete.size()>0){
        entitiesToDelete.pop();
    }
}

// Getters /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Entity* GameState::GetEntity(int id){
    Entity* entity = nullptr;

    if(GetTileByID(id)!=nullptr){
        entity = GetTileByID(id);
    }
    else if(GetImprovement(id)!=nullptr){
        entity = GetImprovement(id);
    }
    else if(GetCity(id)!=nullptr){
        entity = GetCity(id);
    }
    else if(GetArmy(id)!=nullptr){
        entity = GetArmy(id);
    }
    else if(GetUnit(id)!=nullptr){
        entity = GetUnit(id);
    }
    else if(GetBuilding(id)!=nullptr){
        entity = GetBuilding(id);
    }

    return entity;
}

Tile* GameState::GetTileByIndex(int index){
    if(IsValidIndex(index)){
        return tiles.at(index).get();
    }
    else{
        return nullptr;
    }
}

Tile* GameState::GetTileByID(int id){
    for(const auto & tile: tiles){
        if(tile->GetID() == id){
            return tile.get();
        }
    }
    return nullptr;
}

std::vector<int> GameState::GetTileIndicesControlledByPlayer(int player_id){
    std::vector<int> indices;
    for(const auto & tile:tiles){
        if(tile->GetOwner() == player_id){
            indices.push_back(tile->GetIndex());
        }
    }
    return indices;
}

std::vector<Tile*> GameState::GetTilesByID(std::set<int> ids){
    std::vector<Tile*> result;
    std::for_each(ids.begin(), ids.end(), [this, &result](int id){ result.push_back(GetTileByID(id)); });
    return result;
}

std::vector<Tile*> GameState::GetAllTiles(){
    return Utility::UniqueToRaw(tiles);
}

std::vector<Army*> GameState::GetAllArmies(){
    return Utility::UniqueToRaw(armies);
}

///TODO: Make this more efficient
std::set<int> GameState::GetTileIndicesWithinDistance(int position, float distance){
    std::set<int> nearTiles;
    for(unsigned int i=0;i<tiles.size();i++){
        if(Distance(tiles.at(i)->GetIndex(), position)<=distance){
            nearTiles.insert(i);
        }
    }
    return nearTiles;
}

Building* GameState::GetBuilding(int id){
    for(const auto & building: buildings){
        if(building->GetID() == id){
            return building.get();
        }
    }
    return nullptr;
}

City* GameState::GetCity(int id){
    for(const auto & city:cities){
        if(city->GetID() == id){
            return city.get();
        }
    }
    return nullptr;
}

Improvement* GameState::GetImprovement(int id){
    if(id==-1){
        return nullptr;
    }

    for(const auto & imp: improvements ){
        if(imp->GetID() == id){
            return imp.get();
        }
    }

    return nullptr;
}

Improvement* GameState::GetImprovementByIndex(int index){
    Tile* tile = GetTileByIndex(index);
    if(tile){
        if(Improvement* imp = GetImprovement(tile->GetImprovementID())){
            return imp;
        }
        else if(City* city = GetCity(tile->GetImprovementID())){
            return city;
        }
    }
    return nullptr;
}

Army* GameState::GetArmyByIndex(int index){
    for(const auto & army: armies){
        if(army->GetIndex() == index){
            return army.get();
        }
    }
    return nullptr;
}

Army* GameState::GetArmy(int id){
    for(const auto & army:armies){
        if(army->GetID()==id){
            return army.get();
        }
    }
    return nullptr;
}

Unit* GameState::GetUnit(int id){
    for(const auto & unit: units){
        if(unit->GetID() == id){
            return unit.get();
        }
    }
    return nullptr;
}

// PROVINCES ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int GameState::GetProvinceIDContainingTile(int tile_index){
    for(std::pair<int, std::set<int>> pair : provinces){
        if(pair.second.find(tile_index) != pair.second.end()){
            return pair.first;
        }
    }
    return -1;
}

void GameState::AddTileToProvince(int province_id, int tile_index){
    for(auto iter = provinces.begin(); iter!= provinces.end(); iter++){
        iter->second.erase(tile_index);
    }
    if(provinces.find(province_id) == provinces.end()){
        provinces.insert(std::pair<int, std::set<int>>(province_id, std::set<int>()));
    }
    provinces.at(province_id).insert(tile_index);
}

std::set<int> GameState::GetTileIndicesBorderingProvince(int id){
    std::set<int> province = GetProvince(id);
    std::set<int> neighbours;
    std::vector<int> cardinal_offsets;

    cardinal_offsets.push_back(1);
    cardinal_offsets.push_back(-1);
    cardinal_offsets.push_back(mapWidth);
    cardinal_offsets.push_back(-mapWidth);

    for(int index : province){
        for(int offset : cardinal_offsets){
            if(IsValidIndex(index+offset) == false){
                continue;
            }
            if(province.find(index+offset) == province.end()){
                neighbours.insert(index+offset);
            }
        }
    }
    return neighbours;
}

void GameState::RemoveUnitFromSelectedUnits(int unit){
    for(unsigned int i=0;i<selected_units.size();i++){
        if(selected_units.at(i) == unit){
            selected_units.erase(selected_units.begin()+i);
            break;
        }
    }
}

void GameState::ToggleUnitInSelectedUnits(int unit){
//    std::cout<<"ToggleUnitInSelectedUnits disabled\n";
    std::cout<<"unit clicked: "<<unit<<std::endl<<"Selected units: "<<std::endl;
    for(unsigned int i=0; i<selected_units.size();i++){
        std::cout<<selected_units.at(i)<<std::endl;
    }
    if(GetModifierKey("ctrl")){
        for(unsigned int i=0;i<selected_units.size();i++){
            if(selected_units.at(i) == unit){
                selected_units.erase(selected_units.begin()+i);
                return;
                // If it's there remove it
            }
        }
        selected_units.push_back(unit);
    }
    else{
        ClearSelectedUnits();
        selected_units.push_back(unit);
    }
}

std::vector<Entity*> GameState::GetAllEntities(){
    std::vector<Entity*> entities;

    for(const auto & imp:improvements){
        entities.push_back(imp.get());
    }
    for(const auto & city:cities){
        entities.push_back(city.get());
    }
    for(const auto & army:armies){
        entities.push_back(army.get());
    }

    return entities;
}

// PLayers /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void GameState::SwitchToNextPlayer(){
    active_player++;
    if(active_player >= players.size()){
        active_player = 0;
    }
}

void GameState::SelectEntity(int id){
    Entity* entity = GetEntity(id);
    if(selected_entity != -1 and GetEntity(selected_entity)){
        GetEntity(selected_entity)->Deselect();
    }

    if(entity){
        entity->Select();
        selected_entity = entity->GetID();
    }
    else{
        selected_entity = -1;
    }
}
// Keys ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void GameState::SetModifierKey(std::string name, bool down){
    if(modifierKeys.find(name)!=modifierKeys.end()){
        modifierKeys.find(name)->second = down;
    }
}

bool GameState::GetModifierKey(std::string name){
    if(modifierKeys.find(name)!=modifierKeys.end()){
        return modifierKeys.find(name)->second;
    }
    else{
        std::cout<<"Modifier key not found: "<<name<<std::endl;
        return false;
    }
}
